package br.com.gaspar.utils;

import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.text.DateFormat;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;

import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.Days;
import org.joda.time.Hours;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 
 * @author gaspar
 *
 */
public class DataUtil {
	
	protected static final Logger log = LoggerFactory.getLogger(DataUtil.class);

    private long horas = 0;
    private long minutos = 0;
    private long segundos = 0;
    private long elapsedTimeInMillis;
    /** Fator segundo em milesegundo */
    public static final long FATOR_SEGUNDO = 1000;
    /** Fator minuto em milesegundo */
    public static final long FATOR_MINUTO = FATOR_SEGUNDO * 60;
    /** Fator hora em milesegundo */
    public static final long FATOR_HORA = FATOR_MINUTO * 60;

    private DataUtil() {
    }

    public static DataUtil getInstance() {
        return new DataUtil();
    }
    
    

    /**
     *
     * Método que deve ser chamado no "setUp" dos teste JUnit para garantir o
     * funcionamento do Log4j.
     * TODO Ver se existe uma classe "Mock" para o Log4j, para evitar este
     * método.
     */
    public void setAppender() {
        /*FileWriter os = null;
        try {
            ConsoleAppender ap = new ConsoleAppender();
            ap.setName("JUnit");
            os = new FileWriter("junit.log");
            ap.setWriter(os);
            SimpleLayout l = new SimpleLayout();
            ap.setLayout(l);
            log.addAppender(ap);
            log.setLevel(Level.DEBUG);
        } catch (IOException ex) {
            java.util.logging.Logger.getLogger(
        DataUtil.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        } finally {
            try {
                os.close();
            } catch (IOException ex) {
                java.util.logging.Logger.getLogger(
        DataUtil.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
            }
        }*/

    }

    /**
     * Retorna o numero de dias entre as datas passadas como parametro.
     */
    private static BigDecimal getDiferencaEntreDatas(Date dataInicial,
                                                     Date dataFinal) {
        try {
            Double transformarDias = 1000d * 60d * 60d * 24d;

            Double diferencaEmMs = new Double(dataFinal.getTime() - dataInicial.getTime());
            
            Double diferenca = new Double(diferencaEmMs / transformarDias);
            BigDecimal diferencaDias = new BigDecimal(diferenca.toString());

            return diferencaDias;
        } catch (Exception ex) {
            log.error(ex.toString(),ex);
            return null;
        }
    }

    /**
     * Retorna o n&uacute;mero de dias entre as datas passadas como parametro.
     * Quando a diferenca de dias n&atilde;o for exata, o arredondamento a
     * feito para baixo.
     */
    @Deprecated
    public static Integer getDiferencaEmDiasArrendodada(Date dataInicial,
                                                        Date dataFinal) {

        try {
            BigDecimal diferencaDias = getDiferencaEntreDatas(dataInicial, dataFinal);

            MathContext mc = new MathContext(0, RoundingMode.DOWN);

            return diferencaDias.round(mc).intValue();
        } catch (Exception ex) {
            log.error(ex.toString(),ex);
            return null;
        }
    }

    /**
     *
     * Devolve o número de dias entre duas datas. Nota exemplo: Se hoje é
     * 1/12/2004 e amanhã é 2/12/2004, devolve 0 (Zero),
     * pois não há nenhum dia entre as duas datas!
     */
    @Deprecated
    public static Float getDiferencaEmDias(Date dataInicial, Date dataFinal) {
        try {
            BigDecimal diferencaDias = getDiferencaEntreDatas(dataInicial, dataFinal);
            return diferencaDias.floatValue();
        } catch (Exception ex) {
            log.error(ex.toString(),ex);
            return null;
        }
    }

    /**
     *
     * Devolve o número de segundos entre duas data-horas.
     *
     * @param dataInicial
     * @param dataFinal
     * @return dif
     */
    public Float getDiferencaEmSegundos(Date dataInicial, Date dataFinal) {

        log.debug(
                "###################### Entrou no evento segundosEntreDatas");

        try {
            //TimeZone tz = TimeZone.getTimeZone("GMT-03:00");
            //Locale lc = new Locale("br", "BRA");
            //Calendar cal = Calendar.getInstance(tz, lc);

            if (log.isDebugEnabled()) {
                SimpleDateFormat formatter = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
                log.debug("DataInicial:" + formatter.format(dataInicial));
                log.debug("DataFinal:" + formatter.format(dataFinal));
            }

            float dif = (dataFinal.getTime() - dataInicial.getTime()) / 1000F;

            if (log.isDebugEnabled()) {
                log.debug("Diferença entre datas = " + dif);
            }

            return dif;

        } catch (Exception e) {
            log.error(e.toString(), e);
            return null;
        }
    }

    /**
     *
     * Retorna se a segunda data é maior que a primeira
     * @param dataInicial Data inicial
     * @param dataFinal Data Final
     * @return True se a segunda data for maior, incluindo precisão de milionésimos
     * de segundo.
     */
    public boolean dataFimMaiorIni(Date dataInicial, Date dataFinal) {
        try {

            if (getDiferencaEmDias(dataInicial, dataFinal) > 0) {
                return true;
            } else {
                return false;
            }
        } catch (Exception ex) {
            log.error(ex.toString(),ex);
            return false;
        }
    }

    /**
     *
     * Retorna se a segunda data é maior ou igual à primeira
     * @param dataInicial Data inicial
     * @param dataFinal Data Final
     * @return True se a segunda data for maior, incluindo precisão de milionésimos
     * de segundo.
     */
    public boolean dataFimMaiorIgualIni(Date dataInicial, Date dataFinal) {
        try {
            if (getDiferencaEmDias(dataInicial, dataFinal) >= 0) {
                return true;
            } else {
                return false;
            }
        } catch (Exception ex) {
            log.error(ex.toString(),ex);
            return false;
        }
    }

    /**
     *
     * Retorna se a segunda data é maior que a primeira
     * @param dataInicial Data inicial
     * @param dataFinal Data Final
     * @return True se a segunda data for maior, incluindo precisão de
     * milionésimos.
     * de segundo.
     */
    public boolean dataFimMaiorIniEmSegundos(
            Date dataInicial,
            Date dataFinal) {
        try {
            if (getDiferencaEmSegundos(dataInicial, dataFinal) > 0) {
                return true;
            } else {
                return false;
            }
        } catch (Exception ex) {
            log.error(ex.toString(),ex);
            return false;
        }
    }

    /**
     *
     * Retorna se a segunda data é maior ou igual à primeira
     * @param dataInicial Data inicial
     * @param dataFinal Data Final
     * @return True se a segunda data for maior, incluindo precisão de milionésimos
     * de segundo.
     */
    public boolean dataFimMaiorOuIgualIniEmSegundos(
            Date dataInicial,
            Date dataFinal) {
        try {
            if (getDiferencaEmSegundos(dataInicial, dataFinal) >= 0) {
                return true;
            } else {
                return false;
            }
        } catch (Exception ex) {
            log.error(ex.toString(),ex);
            return false;
        }
    }

    /**
     * Retorna uma data passada ou futura, com diferença de anos, em relação á data atual
     *
     * @param dataReferencia Data utilizada como referência para o cálculo
     * @param numeroAnosMinimos Anos a serem adicionados ou subtraidos da dataReferencia
     * @param noPassado true para calcular a data no passado e true para futuro
     * @return a data calculada conforme parâmetros
     */
    public Date dataConformeAnos(Date dataReferencia, long numeroAnosMinimos, boolean noPassado) {

        if(dataReferencia == null)
            return null;
        
        GregorianCalendar cal = new GregorianCalendar();

        cal.setTime(dataReferencia);

        cal.roll(Calendar.YEAR, (int) (noPassado ? (numeroAnosMinimos * -1) : numeroAnosMinimos));

        return cal.getTime();
    }

    /**
     *
     * Devolve uma data que representa o último dia do Mês.
     * @param dataAtual Data corrente
     * @return java.util.Date com dataFinal
     */
    public Date getDataFimMesCorrente(Date dataAtual) {

        if(dataAtual == null)
            return null;

        Calendar c = new GregorianCalendar();
        c.setTime(dataAtual);

        TimeZone tz = TimeZone.getTimeZone("GMT-03:00");
        Locale lc = new Locale("br", "BRA");
        Calendar cal = Calendar.getInstance(tz, lc);
        cal.set(c.get(Calendar.YEAR) - 1900,
                c.get(Calendar.MONTH), c.get(Calendar.DAY_OF_MONTH));

        int diaFinal = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
        Calendar dataFinal = new GregorianCalendar();
        dataFinal.set(c.get(Calendar.YEAR),
                c.get(Calendar.MONTH), diaFinal, 23, 59, 59);
        if (log.isDebugEnabled()) {
            log.debug(
                    "O dia final para o mes="
                    + c.get(Calendar.MONTH)
                    + " eh ="
                    + dataFinal.getTime());
        }

        return dataFinal.getTime();
    }

    /**
     *
     * Retorna a data com os parâmetros fornecidos, com a hora zerada.
     *
     * @param ano Ano
     * @param mes Mês (1 a 12)
     * @param dia (1 a 31)
     * @return Date com a hora zerada
     */
    public static Date getDate(int ano, int mes, int dia) {

        return getDate(ano, mes, dia, 0, 0, 0);

    }

    /**
     *
     * Retorna a datahora com os parâmetros fornecidos.
     *
     * @param ano Ano
     * @param mes Mês (1 a 12)
     * @param dia Dia (1 a 31)
     * @param hora Hora (0 a 23)
     * @param minuto Minuto (0 a 59)
     * @param segundo Segundo (0 a 59)
     * @return Date uma nova data com os parâmetros fornecidos
     */
    public static Date getDate( int ano,
                                int mes,
                                int dia,
                                int hora,
                                int minuto,
                                int segundo) {

        GregorianCalendar cal = new GregorianCalendar();

        if(!isDateValid(ano, mes, dia, hora, minuto, segundo))
            return null;

        cal.set(ano, mes - 1, dia, hora, minuto, segundo);

        return cal.getTime();

    }

    /**
     * Converte uma data do tipo String para java.util.Date
     *
     * @param data          Data no tipo String a ser convertida.
     * @param patternTo     Padr�o do formato da data a ser aplicado.
     *                      Por exemplo, "dd/MM/yyyy HH:mm:ss".
     * @return              Data do tipo java.util.Date convertida.
     */
    public static Date getDate(String data, String patternTo) {

        if (data == null || patternTo == null || data.trim().equals("")) {
            return null;
        }

        SimpleDateFormat sdf = new SimpleDateFormat(patternTo);
        sdf.setLenient(false);
        Date novaData = null;

        try {
            novaData = sdf.parse(data);
        } catch (ParseException ex) {
            log.error(ex.toString(),ex);
        }

        return novaData;
    }

    /**
     * 
     * @param data
     * @param dateFormat
     * @return
     * @throws Exception
     */
    public static Date getDate(String data, int dateFormat) throws Exception {

        try {
            DateFormat df =
                 DateFormat.getDateInstance(dateFormat, new Locale("pt", "BR"));
            df.setLenient(false);
            return df.parse(data);
        } catch (Exception e) {
            log.error(e.toString(),e);
            return null;
        }
    }

    /**
     * Retorna uma String representando a data/hora atual no formato
     * "yyyy-MM-dd HH:mm:ss".
     */
    public static String sysDate() {
        return now("");
    }

    /**
     * Retorna uma String representando a data/hora atual de acordo com a
     * mascara informada.
     */
    public static String sysDate(String mascara) {
        return now(mascara);
    }

    private static String now(String mascara) {
        SimpleDateFormat dateFormatter = null;
        Date today;
        if (mascara.trim().length() > 0) {
            dateFormatter = new SimpleDateFormat(mascara);
        } else {
            dateFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }
        today = new java.util.Date();
        return dateFormatter.format(today);
    }

    /**
     *
     * @param data
     * @param patternTo
     * @return
     */
    public static String getDate(Date data, String patternTo) {
        if (data == null || patternTo == null) {
            return "";
        }
        SimpleDateFormat sdf = new SimpleDateFormat(patternTo, new Locale("pt", "BR"));
        sdf.setLenient(false);
        sdf.applyPattern(patternTo);
        return sdf.format(data);
    }

    /**
     *
     * Formatar data a partir de uma data do tipo String.
     *
     * @param data          Data a ser formatada.
     * @param patternOf     Padr�o atual da data informada.
     * @param patternTo     Padr�o que ser� transformada a data.
     * @return
     */
    public static String transformDateToAnotherPattern(String data, 
                                                       String patternOf,
                                                       String patternTo) {

        try {

            if (data.trim().length() == 0) {
                return "";
            }

            SimpleDateFormat sdf = new SimpleDateFormat(patternOf);
            sdf.setLenient(false);
            Date df = sdf.parse(data);
            sdf.applyPattern(patternTo);

            return sdf.format(df);

        } catch (Exception e) {
            log.error(e.toString(),e);
            return "";
        }
    }

    /**
     *
     * Recebe uma Data e devolve o nome do mês, conforme o locale.
     * @param data
     * @return Nome do Mês
     */
    public String getNomeMes(Date data) {

        if (data == null) {
            return null;
        }

        SimpleDateFormat f =
                new SimpleDateFormat("MMMMMMMMMMMMMMM", new Locale("pt", "BR"));
        return f.format(data);

    }

    /**
     *
     * Recupera lista de datas atraves de uma data inicial e fianal
     * @param inicio Date
     * @param fim Date
     * @return Lista contendo objetos Date.
     */
    @SuppressWarnings({"rawtypes", "unchecked"})
    public List getListaDeDatas(Date inicio, Date fim) {

        if (inicio == null || fim == null) {
            return new ArrayList();
        }

        Map lista = new HashMap();
        
        Float dias = new Float(DataUtil.getDiferencaEmDias(inicio, fim));

        Calendar cal = Calendar.getInstance();

        cal.setTime(inicio);
        lista.put(cal.getTime(), cal.getTime());
        for (int i = 1; dias.intValue() > i - 1; i++) {
            cal.add(Calendar.DAY_OF_YEAR, 1);
            lista.put(cal.getTime(), cal.getTime());
        }
        List listaRetorno = new ArrayList(lista.values());
        Collections.sort(listaRetorno);
        return listaRetorno;
    }

    /**
     * Calcula o tempo decorrido de acordo com a data de inicio e fim fornecida,
     * retornando o tempo formatado em "HH:mm:ss"
     * vosouza 06-05-11 Removido opcao synchronized
     */
    @Deprecated
    public String elapsedTime(Date inicio, Date fim) throws Exception {

        if(inicio == null || fim == null)
            return null;
        
        this.elapsedTimeInMillis = (fim.getTime() - inicio.getTime());

        NumberFormat formatter = NumberFormat.getInstance();
        formatter.setGroupingUsed(false);
        formatter.setMinimumIntegerDigits(2);

        this.horas = this.elapsedTimeInMillis / FATOR_HORA;
        this.minutos = (this.elapsedTimeInMillis % FATOR_HORA) / FATOR_MINUTO;
        this.segundos = (this.elapsedTimeInMillis % FATOR_MINUTO) / FATOR_SEGUNDO;

        return formatter.format(horas) + ":"
                + formatter.format(minutos) + ":"
                + formatter.format(segundos);
    }

    private static boolean isDateValid(int year, int month, int day, int hour,
            int minute, int second) {
        int testYear;
        int testMonth;
        int testDay;
        int testHour;
        int testMinute;
        int testSecond;
        // adjust month because January is equal to 0 in Gregorian Calendar
        month--;
        // instantiate a Gregorian Calendar object with Date to validate
        GregorianCalendar greg = new GregorianCalendar(year, month, day, hour,
                minute, second);
        testYear = greg.get(Calendar.YEAR);
        testMonth = greg.get(Calendar.MONTH);
        testDay = greg.get(Calendar.DATE);
        testHour = greg.get(Calendar.HOUR_OF_DAY); // use 24 hour clock
        testMinute = greg.get(Calendar.MINUTE);
        testSecond = greg.get(Calendar.SECOND);
        if (month != testMonth || day != testDay || year != testYear
                || hour != testHour || minute != testMinute
                || second != testSecond) {
            return false;
        }
        return true;
    }
    
    /**
     * Retorna o ano da data
     * @param data : data que deseja extrair o ano.
     * @return retorna o ano da data passada como parâmetro.
     */
    public static int getAno(Date data){
        Calendar cal = Calendar.getInstance();
        cal.setTime(data);
        return cal.get(Calendar.YEAR);
    }
    
    /**
    * Calcula a diferença entre duas datas em quantidade de horas
    * @param dataInicial
    * @param dataFinal
    * @return
    * 
    * @author gaspar
    */
   public static BigDecimal getDiferencaHoras(Date dataInicial, Date dataFinal) {
	   BigDecimal dia = BigDecimal.valueOf((60 * 60 * 1000));
	   BigDecimal di = BigDecimal.valueOf(dataInicial.getTime());
       BigDecimal df = BigDecimal.valueOf(dataFinal.getTime());
       BigDecimal diferenca = df.subtract(di).divide(dia, 5, 0);
       
       return diferenca;
   }
   
   /**
    * 
    * @param data - Data qe será utilizada no cálculo
    * @param diaMesAno - Se vai ccalcular dia, mês ou ano - Utilizar Calenda.DAY, Calendar.MOUNTH, Calendar.YEAR
    * @param quantidade - o valor a ser somado ou subtraído da data
    * @return
    * 
    * @author gaspar
    */
   public static Date somarDatas(Date data, int diaMesAno, int quantidade){
	   Date retorno = null;
	   
	   Calendar cal = Calendar.getInstance();
	   cal.setTime(data);
	   cal.add(diaMesAno, quantidade);
	   
	   retorno = cal.getTime();
	   
	   return retorno;
   }

   /**
    * 
    * @param data
    * @param hora
    * @param minuto
    * @param segundo
    * @return
    * @author Rogerio
    */
   public static Date configurarData(Date data, Integer hora, Integer minuto, Integer segundo){
     Date retornaData = null;
     Calendar cal = Calendar.getInstance();
     cal.setTime(data);
     cal.set(Calendar.HOUR_OF_DAY, hora);
     cal.set(Calendar.MINUTE, minuto);
     cal.set(Calendar.SECOND, segundo);
     retornaData = cal.getTime();
     return retornaData;
   }
   
   /**
    * Novo métodod utilizando JODA TIME para calcular a diferente de dias entre duas datas.
    * 
    * @param dataInicial - Se valor maior que dataFinal - resultado positivo, senão resultado negativo
    * @param dataFinal
    * @return
    * 
    * @author gaspar
    */
   public static Integer getDiasEntreDatasJoda(Date dataInicial, Date dataFinal) {
	   Integer diferenca = null;
	   DateTimeZone timeZone = DateTimeZone.getDefault();
	   DateTime dtI = new DateTime(dataInicial, timeZone);
	   DateTime dtF = new DateTime(dataFinal, timeZone);
	   try {
		   diferenca = Days.daysBetween(dtI, dtF).getDays();
		   Integer horas = Hours.hoursBetween(dtI, dtF).getHours();
		   System.out.println(horas);
		   
	   }catch (Exception ex) {
		   log.error(ex.toString(),ex);
		   
	   }
	   return diferenca;
	}
   
   /**
    * 
    * @param data
    * @param hora
    * @param minuto
    * @param segundo
    * @return
    */
   public static Date configurarDataJoda(Date data, Integer hora, Integer minuto, Integer segundo){
	   DateTime dataRetorno = new DateTime(data);
	   dataRetorno = dataRetorno.withHourOfDay(hora).withMinuteOfHour(minuto).withSecondOfMinute(segundo);
	   /*Date retornaData = null;
	   Calendar cal = Calendar.getInstance();
	   cal.setTime(data);
	   cal.set(Calendar.HOUR_OF_DAY, hora);
	   cal.set(Calendar.MINUTE, minuto);
	   cal.set(Calendar.SECOND, segundo);
	   retornaData = cal.getTime();*/
	   return dataRetorno.toDate();
   }
}